/// <summary>
/// Copyright 2010 Ivo Limmen (ivo@limmen.org)
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
/// 
/// http://www.apache.org/licenses/LICENSE-2.0
/// 
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
/// </summary>

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;

namespace Util.Reflection
{
	/// <summary>
	/// This class can search for Types that implement a specific interface.
	/// It searches all DLL's within a specified directory and instanciates all found types and
	/// returns a list of implementations.
	/// </summary>
	public class ImplementationFinder : IDisposable
	{
		private string currentDirectory;

		private bool ignoreInstanciationErrors = false;

		public ImplementationFinder(string directory, bool ignoreInstanciationErrors)
		{
			this.currentDirectory = directory;
			this.ignoreInstanciationErrors = ignoreInstanciationErrors;
		}

        Assembly AssemblyResolver(object sender, ResolveEventArgs args)
        {
            string fileName = args.Name.Split(',')[0];

            foreach (string file in Directory.GetFiles(this.currentDirectory, "*.dll"))
            {
                if (file.Contains(fileName))
                {
                    return Assembly.LoadFile(file);
                }
            }

            throw new TypeLoadException(string.Format("Assembly {0} could not be found!", fileName));
        }

		public List<T> GetImplementations<T>()
		{
            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(AssemblyResolver);

            Type interfaceType = typeof(T);
			String name = interfaceType.Name;
			List<T> impls = new List<T>();

			foreach (string file in Directory.GetFiles(this.currentDirectory, "*.dll"))
			{
				Assembly assembly = Assembly.LoadFile(file);

                try
                {
                    foreach (Type type in assembly.GetTypes())
                    {
                        Type typeOfInterface = type.GetInterface(name);

                        if (typeOfInterface != null && typeOfInterface.Equals(interfaceType))
                        {
                            ConstructorInfo constructorInfo = type.GetConstructor(new Type[] { });

                            if (constructorInfo != null)
                            {
                                try
                                {
                                    impls.Add((T)constructorInfo.Invoke(new object[] { }));
                                }
                                catch (Exception e)
                                {
                                    if (!ignoreInstanciationErrors)
                                    {
                                        throw e;
                                    }
                                }
                            }
                            else
                            {
                                Debug.WriteLine("No default constructor found in type: " + type.Name);
                            }
                        }
                    }
                }
                catch(Exception) 
                {
                    // ignore
                }
			}
            AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(AssemblyResolver);			

			return impls;
		}

        public void Dispose()
        {
        }
    }
}
